use std::collections::HashMap;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Result};
use chrono::Local;
use console::{style, Style};
use dialoguer::theme::ColorfulTheme;
use dialoguer::{MultiSelect, Select};
use regex::Regex;
use toml::Value;

use crate::service::{AppData, ServerItem};
use crate::ssh::SshUtil;
use crate::utils::CmdUtil;
use crate::utils::{read_config, DeployConfig, Profile, FILE_DELIMITER};
use crate::{error, log, success, WORK_DIR};

fn get_theme() -> ColorfulTheme {
    ColorfulTheme {
        defaults_style: Style::new().for_stderr().cyan(),
        prompt_style: Style::new().for_stderr().bold(),
        prompt_prefix: style("?".to_string()).for_stderr().yellow(),
        prompt_suffix: style("›".to_string()).for_stderr().black().bright(),
        success_prefix: style("✔".to_string()).for_stderr().blue(),
        success_suffix: style("·".to_string()).for_stderr().black().bright(),
        error_prefix: style("✘".to_string()).for_stderr().red(),
        error_style: Style::new().for_stderr().red(),
        hint_style: Style::new().for_stderr().black().bright(),
        values_style: Style::new().for_stderr().blue(),
        active_item_style: Style::new().for_stderr().cyan(),
        inactive_item_style: Style::new().for_stderr(),
        active_item_prefix: style("❯".to_string()).for_stderr().blue(),
        inactive_item_prefix: style(" ".to_string()).for_stderr(),
        checked_item_prefix: style("✔".to_string()).for_stderr().blue(),
        unchecked_item_prefix: style(" ".to_string()).for_stderr().black(),
        picked_item_prefix: style("❯".to_string()).for_stderr().blue(),
        unpicked_item_prefix: style(" ".to_string()).for_stderr(),
    }
}

pub struct WorkUtil {
    config: DeployConfig,
    active: String,
    app: AppData,
}

impl WorkUtil {
    pub fn new() -> Result<Self> {
        Ok(WorkUtil {
            active: "".to_string(),
            config: read_config()?,
            app: AppData::read_data()?,
        })
    }
    pub async fn run(&mut self) -> Result<()> {
        self.check_config()?;

        let profile = self.choose_profile()?;
        // let select: Vec<String> = profile.server.iter().map(|x| x.name.clone()).collect();
        let select = self.choose_server(&profile)?;
        let deploy_path: HashMap<String, String> = profile
            .server
            .iter()
            .map(|x| (x.name.clone(), x.deploy_path.clone()))
            .collect();
        let choose_server: Vec<(String, ServerItem)> = self
            .app
            .servers
            .iter()
            .filter(|item| select.contains(&item.label))
            .map(|item| {
                let mut server = item.clone();
                server.deploy_path = deploy_path
                    .get(&item.label)
                    .as_ref()
                    .unwrap_or_else(|| panic!("获取失败！"))
                    .to_string();
                (item.label.clone(), server)
            })
            .collect();
        if choose_server.is_empty() {
            return Err(anyhow!("请配置需要部署的服务器！"));
        }

        // 设置本地命令行环境变量
        let mut envs = profile.env.clone();
        envs.insert("active".to_string(), Value::from(self.active.clone()));
        let execution = self.config.execution.clone();
        let mut cmd = CmdUtil::new(WORK_DIR.get().unwrap().clone(), envs, execution)?;
        CmdUtil::set_cmd_encode()?;

        let local_command = self.build_cmds(
            &self.config.local.command,
            "",
            self.config.target_name.as_str(),
            self.config.target_path.as_str(),
        )?;

        if !local_command.is_empty() {
            cmd.exec_cmds(local_command)?;
        }

        for (label, server) in choose_server {
            match self.deploy(&server).await {
                Ok(()) => success(format!("{}部署成功！", label)),
                Err(err) => {
                    error(format!("{}部署失败！{}", label, err));
                }
            };
        }

        let after_command = self.config.after.command.clone();
        let after_command = self.build_cmds(
            &after_command,
            "",
            self.config.target_name.as_str(),
            self.config.target_path.as_str(),
        )?;
        if !after_command.is_empty() {
            cmd.debug = false;
            cmd.exec_cmds(after_command)?;
        }
        Ok(())
    }
    async fn deploy(&self, server: &ServerItem) -> Result<()> {
        let deploy_path = server.deploy_path.clone();
        let remote = self.config.remote.clone();
        let command = self.build_cmds(
            &self.config.remote.command,
            deploy_path.as_str(),
            self.config.target_name.as_str(),
            self.config.target_path.as_str(),
        )?;

        let local_path = self.get_local_path();
        let remote_path = Path::new(&deploy_path).join(self.config.target_name.clone());

        match SshUtil::login_server(server) {
            Ok(mut ssh) => {
                let mut cmds = vec![];
                if remote.backup {
                    cmds.push(format!("mkdir -p {}", remote.backup_file_path.clone()));
                    let cmd = format!(
                        "cp {}/{} {}/{}_{}",
                        deploy_path,
                        self.config.target_name,
                        remote.backup_file_path,
                        Local::now().format("%Y%m%d%H%M%S"),
                        self.config.target_name.clone()
                    );
                    cmds.push(cmd);
                }
                if local_path.is_file() {
                    log(format!("开始上传文件：{:?}!", local_path));
                    ssh.upload_file(&local_path, &PathBuf::from(&deploy_path), &remote_path)
                        .await?;
                    success("文件上传完成！".to_string());
                }
                cmds.extend(command);
                ssh.auto_exec(cmds, Some(deploy_path)).await?;
                Ok(())
            }
            Err(err) => Err(anyhow!(format!(
                "服务器[{}]登录失败，{}",
                server.label,
                err.to_string()
            ))),
        }
    }

    fn build_cmds(
        &self,
        cmds: &[String],
        deploy_path: &str,
        target_name: &str,
        target_path: &str,
    ) -> Result<Vec<String>> {
        let reg1 = Regex::new(r"(\{deployPath})")?;
        let reg2 = Regex::new(r"(\{targetName})")?;
        let reg3 = Regex::new(r"(\{targetPath})")?;

        let new_cmds: Vec<String> = cmds
            .iter()
            .map(|x| x.as_str().to_string())
            .map(|x| reg1.replace_all(&x, deploy_path).to_string())
            .map(|x| reg2.replace_all(&x, target_name).to_string())
            .map(|x| reg3.replace_all(&x, target_path).to_string())
            .filter(|x| !x.is_empty())
            .collect();
        Ok(new_cmds)
    }
    fn get_local_path(&self) -> PathBuf {
        let current_dir = WORK_DIR
            .get()
            .unwrap_or_else(|| panic!("获取工作目录失败！"));
        let local_path = if self.config.target_path.is_empty() {
            PathBuf::from(current_dir)
        } else {
            let temp = self.config.target_path.clone();
            if temp.starts_with(FILE_DELIMITER) {
                PathBuf::from(&temp)
            } else {
                let tmp = Path::new(current_dir).join(temp);
                tmp.canonicalize().expect("本地路径错误！")
            }
        };
        if self.config.target_name.is_empty() {
            local_path
        } else {
            local_path.join(self.config.target_name.clone())
        }
    }
    fn _get_remote_path(path: &String, user: &String) -> PathBuf {
        let root_path = match user.eq("root") {
            true => "/root".to_string(),
            false => format!("/home/{}", user),
        };
        let real_path = if path.is_empty() {
            root_path
        } else if path.starts_with("./") || path.starts_with("~/") {
            match user.eq("root") {
                true => path.replace("./", "/root").replace("~/", "/root"),
                false => path.replace("./", &root_path).replace("~/", &root_path),
            }
        } else {
            path.to_string()
        };
        PathBuf::from(real_path)
    }
    fn check_config(&self) -> Result<()> {
        if self.config.profile.is_empty() {
            return Err(anyhow!("运行环境不能为空！"));
        }
        Ok(())
    }
    fn choose_profile(&mut self) -> Result<Profile> {
        let profiles = self.config.profile.clone();
        let keys: Vec<&String> = profiles.keys().collect();
        let key = if keys.len() > 1 {
            let selection = Select::with_theme(&get_theme())
                .with_prompt("请选择运行环境?")
                .default(0)
                .items(&keys)
                .interact()?;
            keys[selection]
        } else {
            keys[0]
        };

        self.active = key.clone().to_string();
        success(format!("当前选择运行环境：{}", key));
        match profiles.get(key) {
            Some(profile) => Ok(profile.clone()),
            None => Err(anyhow!("获取失败！")),
        }
    }

    fn choose_server(&mut self, profile: &Profile) -> Result<Vec<String>> {
        let servers: Vec<String> = profile.server.iter().map(|x| x.name.clone()).collect();
        let selection = MultiSelect::with_theme(&get_theme())
            .with_prompt("请选择部署服务器！")
            .items(&servers)
            .defaults(&[true])
            .interact()?;
        let result: Vec<String> = selection
            .iter()
            .map(|x| servers.get(*x).unwrap().clone())
            .collect();
        Ok(result)
    }
}
